package com.xcity.game.item;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xcity.db.entity.EntityState;
import com.xcity.db.entity.Player;
import com.xcity.db.mapper.ItemMapper;
import com.xcity.game.common.MessageConst;
import com.xcity.game.event.GameEvents;
import com.xcity.game.item.listener.BagListener;
import com.xcity.game.mail.MailAttach;
import com.xcity.game.mail.MailService;
import com.xcity.game.relation.RelationService;
import com.xcity.game.vip.VipTemplate;
import com.xcity.game.vip.VipUtils;
import com.xcity.pomelo.push.SyncPush;
import com.xcity.pomelo.push.sync.CustomSync;
import com.xcity.pomelo.push.sync.LongSync;
import com.xcity.pomelo.push.sync.MapSync;
import com.xcity.util.DBUtils;

import naga.x.App;
import naga.x.common.Initializable;
import naga.x.game.template.TemplateService;

/**
 * 背包
 * @author yang.li
 *
 */
public class Bag implements Initializable {

	private Player owner;
	private List<GameItem> items;
	private List<GameItem> pendingRemoveItems;
	private Lock lock = new ReentrantLock();
	
	public static final int BAG_GRID_MAX = 100;
	protected static final Logger LOG = LoggerFactory.getLogger(Bag.class);
	
	// 等待删除物品数量每超过100个时需异步从数据库删除
	private Runnable syncRemovesThread = new Runnable() {
		@Override
		public void run() {
			GameItem[] items = null;
			lock.lock();
			try {
				items = pendingRemoveItems.toArray(new GameItem[pendingRemoveItems.size()]);
				pendingRemoveItems.clear();
			} finally {
				lock.unlock();
			}
			if (items != null) {
				for (GameItem item : items) {
					syncItem(-1, item);
				}
			}
		}
	};
	
	public Bag(Player owner) {
		this.owner = owner;
		this.items = new ArrayList<GameItem>(getMaxGridSize());
		this.pendingRemoveItems = new ArrayList<GameItem>();
		init();
	}
	
	@Override
	public void init() {
		List<com.xcity.db.entity.Item> entities = DBUtils.selectList(ItemMapper.STATEMENT_FIND_BY_OWNER, owner.getId());
		if (entities == null || entities.isEmpty()) {
			return;
		}
		for (int i = 0, size = entities.size(); i < size; i++) {
			com.xcity.db.entity.Item entity = entities.get(i);
			GameItem item = new GameItem(this, entity);
			items.add(item);
		}
	}
	
	public Player getOwner() {
		return owner;
	}
	
	public boolean addItemWithoutException(int itemTemplateId, int count, String cause) {
		return addItemWithoutException(itemTemplateId, count, cause, null);
	}
	
	public boolean addItemWithoutException(int itemTemplateId, int count, String cause, BagListener listener) {
		ItemTemplate template = App.getApp().getServiceManager().get(TemplateService.class).get(ItemTemplate.class, itemTemplateId);
		return addItemWithoutException(template, count, cause, listener);
	}
	
	public boolean addItemWithoutException(ItemTemplate template, int count, String cause) {
		return addItemWithoutException(template, count, cause, null);
	}
	
	public boolean addItemWithoutException(ItemTemplate template, int count, String cause, BagListener listener) {
		try {
			addItem(template, count, cause, listener);
			return true;
		} catch (ItemException e) {
			LOG.error("[ADDITEM ERROR]ID[" + owner.getId() + "]ITEM[" + (template != null ? template.getId() : -1) + "=" + count + "]CAUSE[" + cause + "]", e);
		}
		return false;
	}
	
	/**
	 * 加物品 背包满了则发邮件
	 * @return
	 */
	public boolean addItemOrSendMail(int itemTemplateId, int count, String cause) {
		return addItemOrSendMail(itemTemplateId, count, cause, null);
	}
	
	public boolean addItemOrSendMail(int itemTemplateId, int count, String cause, BagListener listener) {
		ItemTemplate template = App.getApp().getServiceManager().get(TemplateService.class).get(ItemTemplate.class, itemTemplateId);
		return addItemOrSendMail(template, count, cause, listener);
	}
	
	public boolean addItemOrSendMail(ItemTemplate template, int count, String cause) {
		return addItemOrSendMail(template, count, cause, null);
	}
	
	public boolean addItemOrSendMail(ItemTemplate template, int count, String cause, BagListener listener) {
		try {
			addItem(template, count, cause, listener);
			return true;
		} catch (ItemException e) {
			if(e.getMessage().equals(MessageConst.BAG_FULL)){
				MailService ms = App.getApp().getServiceManager().get(MailService.class);
				MailAttach attach = new MailAttach();
				attach.addItem(template.getId(), count);
				ms.sendSystemMail(getOwner().getId(), MessageConst.BAG_FULL_SENDMAIL, "", attach);
				return true;
			}else{
				LOG.error("[ADDITEM ERROR]ID[" + owner.getId() + "]ITEM[" + (template != null ? template.getId() : -1) + "=" + count + "]CAUSE[" + cause + "]", e);
			}
		}
		return false;
	}
	
	public void addItem(int itemTemplateId, int count, String cause) throws ItemException {
		addItem(itemTemplateId, count, cause, null);
	}
	
	public void addItem(int itemTemplateId, int count, String cause, BagListener listener) throws ItemException {
		ItemTemplate template = App.getApp().getServiceManager().get(TemplateService.class).get(ItemTemplate.class, itemTemplateId);
		addItem(template, count, cause, listener);
	}
	
	public void addItem(ItemTemplate template, int count, String cause) throws ItemException {
		addItem(template, count, cause, null);
	}
	
	public void addItem(ItemTemplate template, int count, String cause, BagListener listener) throws ItemException {
		if (count <= 0) {
			throw new ItemException(MessageConst.ILLEGAL_OPERATION);
		}
		if (template == null) {
			throw new ItemException(MessageConst.ITEM_NOT_EXIST);
		}
		if (!template.isAutoUse()) {
			int needBagGridSize = count / template.getStack();
			if (needBagGridSize <= 0) {
				needBagGridSize = 1;
			}
			if (getFreeSize() < needBagGridSize) { // 背包满
				throw new ItemException(MessageConst.BAG_FULL);
			}
		}
		int ccount = count;
		LOG.info("[ADDITEM]ID[{}]ITEM[{}={}]CAUSE[{}]", owner.getId(), template.getId(), count, cause);
		try {
			if (template.isAutoUse()
					&& template.getTarget() == GameItem.TARGET_SELF
					&& useItem(owner, new GameItem(this, template, count), count, cause, listener)) {
				return;
			}
		} catch (ItemException e) {
			// 如果自动使用出错则继续尝试直接加进背包
		}
		lock.lock();
		try {
			if (template.getStack() > 1) {
				GameItem item = getItemByTemplateId(template.getId());
				int c; // 可叠加的最大数量
				if (item != null && (c = template.getStack() - item.getCount()) > 0) { // 可叠加
					item.count += count > c ? c : count;
					itemChanged(item);
					if ((count -= c) <= 0) { // count > 0: 剩余未加入背包的
//						return;
					}
				}
			}
			while (count > 0) {
				GameItem item = new GameItem(this, template, count > template.getStack() ? template.getStack() : count);
				items.add(item);
				itemAdded(item);
				count -= item.getCount();
			}
		} finally {
			lock.unlock();
		}
		if (listener != null) {
			listener.itemAdded(template, ccount);
		}
		App.getApp().getEventManager().addEvent(GameEvents.EVENT_ADD_ITEM, owner, template, ccount);
	}
	
	public boolean removeItem(int itemTemplateId, int count, String cause) {
		if (count <= 0) {
			return false;
		}
		lock.lock();
		try {
			List<GameItem> pendings = new ArrayList<GameItem>(2);
			int sum = 0;
			for (int i = 0, size = items.size(); i < size; i++) {
				GameItem item = items.get(i);
				if (item.getTemplate().getId() == itemTemplateId
						&& item.getState() != EntityState.DELETE) {
					pendings.add(item);
					if ((sum += item.getCount()) >= count) {
						break;
					}
				}
			}
			if (sum < count) {
				return false;
			}
			for (int i = 0, size = pendings.size(); i < size; i++) {
				GameItem item = pendings.get(i);
				count -= removeItem(item, count, cause);
				if (count <= 0) {
					return true;
				}
			}
			return false;
		} finally {
			lock.unlock();
		}
	}
	
	/**
	 * 返回数量不足的物品
	 * @param batch
	 * @return
	 */
	public ItemInfo removeItems(String cause, ItemInfo... batch) {
		if (batch == null || batch.length == 0) {
			return null;
		}
		lock.lock();
		try {
			// 先遍历检查一遍是否充足，再遍历删除
			for (ItemInfo info : batch) {
				boolean enough = hasEnough(info.id, info.count);
				if (!enough) {
					return info;
				}
			}
			for (ItemInfo info : batch) {
				if (!removeItem(info.id, info.count, cause)) {
					return info;
				}
			}
		} finally {
			lock.unlock();
		}
		return null;
	}
	
	public GameItem removeItem(long itemInstanceId, int count, String cause) {
		if (count <= 0) {
			return null;
		}
		lock.lock();
		try {
			GameItem item = getItem(itemInstanceId);
			if (item != null && item.getCount() >= count) {
				removeItem(item, count, cause);
				return item;
			}
		} finally {
			lock.unlock();
		}
		return null;
	}
	
	public GameItem useItem(Player target, long itemInstanceId, int count, String cause) throws ItemException {
		return useItem(target, itemInstanceId, count, cause, null);
	}
	
	public GameItem useItem(Player target, long itemInstanceId, int count, String cause, BagListener listener) throws ItemException {
		if (count <= 0) {
			throw new ItemException(MessageConst.ILLEGAL_OPERATION);
		}
		lock.lock();
		try {
			GameItem item = getItem(itemInstanceId);
			if (item == null || item.getCount() < count) {
				throw new ItemException(MessageConst.ITEM_NOT_ENOUGH2);
			}
			if (useItem(target, item, count, cause, listener)) {
				removeItem(item, count, cause);
				return item;
			}
			throw new ItemException(MessageConst.ITEM_USE_FAILED);
		} finally {
			lock.unlock();
		}
	}
	
	public void useItem(Player target, int itemTemplateId, int count, String cause) throws ItemException {
		useItem(target, itemTemplateId, count, cause, null);
	}
	
	public void useItem(Player target, int itemTemplateId, int count, String cause, BagListener listener) throws ItemException {
		if (count <= 0) {
			throw new ItemException(MessageConst.ILLEGAL_OPERATION);
		}
		lock.lock();
		try {
			ItemTemplate template = App.getApp().getServiceManager().get(TemplateService.class).get(ItemTemplate.class, itemTemplateId);
			if (template == null) {
				throw new ItemException(MessageConst.ITEM_NOT_EXIST);
			}
			int diff;
			if ((diff = count - countOf(itemTemplateId)) > 0) {
				throw new ItemException(MessageFormat.format(MessageConst.USE_ITEM_WHEN_NOT_ENOUGH, count, template.getName(), diff));
			}
			for (int i = 0, size = items.size(); count > 0 && i < size; i++) {
				GameItem item = items.get(i);
				if (item.getCount() > 0 && item.getState() != EntityState.DELETE) {
					int useCount = item.getCount() < count ? item.getCount() : count;
					count -= useCount;
					if (!useItem(target, item, useCount, cause, listener)) {
						throw new ItemException(MessageConst.ITEM_USE_FAILED);
					}
					removeItem(item, useCount, cause);
				}
			}
		} finally {
			lock.unlock();
		}
	}
	
	public GameItem getItem(long itemInstanceId) {
		lock.lock();
		try {
			for (int i = 0, size = items.size(); i < size; i++) {
				GameItem item = items.get(i);
				if (item.getId() == itemInstanceId
						&& item.getState() != EntityState.DELETE) {
					return item;
				}
			}
			return null;
		} finally {
			lock.unlock();
		}
	}
	
	public List<GameItem> getItems() {
		return Collections.unmodifiableList(items);
	}
	
	public boolean isFull() {
		lock.lock();
		try {
			return items.size() >= getMaxGridSize();
		} finally {
			lock.unlock();
		}
	}
	
	public int getFreeSize() {
		lock.lock();
		try {
			int free = getMaxGridSize() - items.size();
			return free > 0 ? free : 0;
		} finally {
			lock.unlock();
		}
	}
	
	public int getMaxGridSize() {
		VipTemplate vt = VipUtils.getTemplate(owner.getVipLvl());
		return BAG_GRID_MAX + (vt != null ? vt.getBagGridExtendNum() : 0);
	}
	
	public int size() {
		lock.lock();
		try {
			return items.size();
		} finally {
			lock.unlock();
		}
	}
	
	public boolean hasEnough(int itemTemplateId, int count) {
		if (count <= 0) {
			return false;
		}
		lock.lock();
		try {
			int sum = 0;
			for (int i = items.size() - 1; i >= 0; i--) {
				GameItem item = items.get(i);
				if (item.getTemplate().getId() == itemTemplateId
						&& item.getState() != EntityState.DELETE) {
					sum += item.getCount();
					if (sum >= count) {
						return true;
					}
				}
			}
			return sum >= count;
		} finally {
			lock.unlock();
		}
	}
	
	public int countOf(int itemTemplateId) {
		lock.lock();
		try {
			int sum = 0;
			for (int i = items.size() - 1; i >= 0; i--) {
				GameItem item = items.get(i);
				if (item.getTemplate().getId() == itemTemplateId
						&& item.getState() != EntityState.DELETE) {
					sum += item.getCount();
				}
			}
			return sum;
		} finally {
			lock.unlock();
		}
	}
	
	public void save() {
		lock.lock();
		try {
			List<GameItem> updates = items;
			for (int i = 0, size = updates.size(); i < size; i++) {
				syncItem(i, updates.get(i));
			}
			List<GameItem> removes = pendingRemoveItems;
			for (int i = 0, size = removes.size(); i < size; i++) {
				syncItem(i, removes.get(i));
			}
			pendingRemoveItems.clear();
		} finally {
			lock.unlock();
		}
	}
	
	//------ PRIVATE ------
	/**
	 * 删除物品
	 * @param item
	 * @param count
	 * @return 实际被删除数量(<= count)
	 */
	private int removeItem(GameItem item, int count, String cause) {
		item.count -= (count = count > item.count ? item.count : count);
		if (item.count <= 0) {
			item.state = EntityState.DELETE;
			pendingRemoveItems.add(item);
			items.remove(item);
			if (pendingRemoveItems.size() >= 100) {
				App.getApp().getThreadPool().execute(syncRemovesThread);
			}
			itemRemoved(item);
		} else {
			itemChanged(item);
		}
		App.getApp().getEventManager().addEvent(GameEvents.EVENT_REMOVE_ITEM, owner, item.getTemplate(), count);
		LOG.info("[REMOVEITEM]ID[{}]ITEM[{}={}]CAUSE[{}]", owner.getId(), item.getTemplate().getId(), count, cause);
		return count;
	}
	
	private boolean useItem(Player target, GameItem item, int count, String cause, BagListener listener) throws ItemException {
		ItemEffect effect = ItemEffectFactory.create(item.getTemplate());
		if (effect == null) { // 物品不能被使用
			throw new ItemException(MessageConst.ITEM_CANNOT_USE);
		}
		switch (item.getTemplate().getTarget()) {
			case GameItem.TARGET_SELF:
				if (target != owner) throw new ItemException(MessageConst.ITEM_USE_ONLY_FOR_SELF);
				break;
			case GameItem.TARGET_FRIEND:
				boolean isFriend = App.getApp().getServiceManager().get(RelationService.class).isFriend(owner, target);
				if (!isFriend) throw new ItemException(MessageConst.ITEM_USE_ONLY_FOR_FRIEND);
				break;
			case GameItem.TARGET_NOT_FRIEND:
				isFriend = App.getApp().getServiceManager().get(RelationService.class).isFriend(owner, target);
				if (isFriend) throw new ItemException(MessageConst.ITEM_USE_ONLY_FOR_NOT_FRIEND);
				break;
			case GameItem.TARGET_ENEMY:
				throw new ItemException(MessageConst.ITEM_USE_NOT_SUPPORT_FOR_ENEMY);
			case GameItem.TARGET_OTHER:
				if (target == owner) throw new ItemException(MessageConst.ITEM_USE_ONLY_FOR_OTHER);
				break;
		}
		boolean succ = effect.effect(target, item, count, listener);
		LOG.info("[USEITEM]ID[{}]TARGET[{}]ITEM[{}={}]CAUSE[{}]", owner.getId(), target.getId(), item.getTemplate().getId(), count, cause);
		return succ;
	}
	
	private GameItem getItemByTemplateId(int itemTemplateId) {
		lock.lock();
		try {
			for (int i = items.size() - 1; i >= 0; i--) {
				GameItem item = items.get(i);
				if (itemTemplateId == item.getTemplate().getId()
						&& item.getState() != EntityState.DELETE) {
					return item;
				}
			}
			return null;
		} finally {
			lock.unlock();
		}
	}
	
	private void itemChanged(GameItem item) {
		owner.send(new MapSync(SyncPush.SYNC_ITEM_COUNT, item.getId(), item.getCount()));
	}
	
	private void itemAdded(GameItem item) {
		owner.send(new CustomSync(SyncPush.SYNC_ITEM_ADDED, new com.xcity.pomelo.response.BagInfoResponse.ItemInfo(item)));
	}
	
	private void itemRemoved(GameItem item) {
		owner.send(new LongSync(SyncPush.SYNC_ITEM_REMOVED, item.getId()));
	}
	
	private void syncItem(int pos, GameItem item) {
		if (item.getState() == EntityState.DELETE) {
			DBUtils.delete(item.getId(), ItemMapper.class);
			return;
		}
		com.xcity.db.entity.Item entity = new com.xcity.db.entity.Item();
		entity.setId(item.getId());
		entity.setItemId(item.getTemplate().getId());
		entity.setOwnerId(owner.getId());
		entity.setBagId((byte) 0);
		entity.setPos(pos);
		entity.setCount(item.getCount());
		entity.setCreateTime(item.getCreateTime());
		switch (item.getState()) {
			case INSERT:
				item.state = EntityState.UPDATE;
				DBUtils.save(entity, ItemMapper.class);
				break;
			case UPDATE:
				DBUtils.update(entity, ItemMapper.class);
				break;
			case DELETE:
				DBUtils.delete(entity.getId(), ItemMapper.class);
				break;
		}
	}
	//------ PRIVATE ------
	
}
